5-8 Class类(修饰符、构建函数、接口扩展)
1. 类基础与修饰符
1.1 类定义与实例化
class Person {
name = "Tom Mark"; // 默认public修饰符
getName() { return this.name; }
}
const person = new Person(); // 实例化
console.log(person.getName()); // 输出: Tom Mark
typescript
扩展说明:
- 类名规范:TypeScript 推荐使用帕斯卡命名法(PascalCase),即首字母大写,如
Person
或UserService
。但在某些团队约定中,也可能使用小写字母开头(如示例中的person
),但需确保团队一致性。 - 默认值初始化:成员变量可以直接在类中初始化,如
name = "Tom Mark"
,这会在实例化时自动赋值。 - 方法定义:类中的方法(如
getName
)默认绑定到实例,通过this
访问实例属性。
💡 提示:
在 TypeScript 中,类的成员变量和方法默认是 public
的,但显式声明修饰符(如 public
、private
)可以提高代码可读性。
1.2 访问修饰符
1.2.1 public
class User {
public id = 1; // 显式声明为public
}
const user = new User();
console.log(user.id); // ✅ 允许访问
typescript
扩展说明:
- 默认行为:如果不写修饰符,成员变量和方法默认为
public
。 - 适用场景:当需要允许外部直接访问或修改成员变量时使用。
- 最佳实践:尽管
public
是默认的,显式声明可以明确意图,避免歧义。
💡 提示:
在大型项目中,显式标注 public
可以让代码更易维护,尤其是在团队协作时。
1.2.2 private
class User {
private secret = "confidential";
revealSecret() {
return this.secret; // ✅ 类内可访问
}
}
const user = new User();
console.log(user.secret); // ❌ 编译错误
console.log(user.revealSecret()); // ✅ 通过公有方法访问
typescript
扩展说明:
- 严格封装:
private
成员只能在类内部访问,外部直接访问会报错。 - 使用场景:适用于敏感数据或内部实现细节,避免外部直接修改。
- 编译后行为:TypeScript 的
private
是编译时检查,运行时仍可通过某些方式访问(如user["secret"]
)。
💡 提示:
如果需要更严格的私有性,可以使用 ECMAScript 的私有字段语法(#secret
),这是真正的运行时私有。
1.2.3 protected
class Parent {
protected asset = "family property";
}
class Child extends Parent {
getAsset() { return this.asset; } // ✅ 子类可访问
}
const child = new Child();
console.log(child.getAsset()); // ✅ 通过子类方法访问
console.log(child.asset); // ❌ 外部直接访问报错
typescript
扩展说明:
- 继承链可见性:
protected
成员对子类可见,但对外部不可见。 - 设计用途:适用于需要被子类继承但不暴露给外部的成员。
- 与
private
对比:private
仅限当前类,protected
允许子类访问。
💡 提示:
在框架或库开发中,protected
常用于定义可扩展的基类。
1.2.4 readonly
class Config {
readonly apiKey = "ABC-123";
constructor(apiKey?: string) {
if (apiKey) this.apiKey = apiKey; // ✅ 构造函数内可赋值
}
}
const config = new Config("XYZ-456");
console.log(config.apiKey); // ✅ 输出: XYZ-456
config.apiKey = "NEW-KEY"; // ❌ 编译错误
typescript
扩展说明:
- 初始化规则:
readonly
成员必须在声明时或构造函数内初始化。 - 不可变性:初始化后不可修改,类似
const
但作用于类成员。 - 使用场景:适用于配置项、常量或需要保证不可变性的数据。
💡 提示:readonly
可以与 public
、private
或 protected
结合使用,如 public readonly id: string
。
1.3 修饰符组合使用
class Example {
public readonly id: string; // 公开但不可修改
private static count = 0; // 静态私有变量
protected logLevel = "info"; // 子类可访问
constructor(id: string) {
this.id = id;
Example.count++;
}
static getCount() {
return Example.count; // ✅ 静态方法访问静态私有变量
}
}
typescript
扩展说明:
- 静态成员:用
static
修饰的成员属于类本身,而非实例。 - 组合修饰符:如
public readonly
或private static
,可以灵活控制成员的可见性和可变性。
💡 提示:
静态私有成员常用于实现单例模式或全局计数器等场景。
1.4 常见问题解答
Q1: 为什么 private
成员在运行时仍可访问?
TypeScript 的 private
是编译时检查,编译后的 JavaScript 代码中并无真正的私有性。如需运行时私有,使用 #
语法:
class Secure {
#secret = "hidden";
}
typescript
Q2: readonly
和 const
有什么区别?
const
:用于变量声明,作用域为块级。readonly
:用于类成员,作用域为实例或类。
Q3: 子类能否重写 protected
方法?
可以,但需保持兼容性(如参数和返回类型一致)。
1.5 延伸学习资源
2. 类继承机制
2.1 extends关键字
class Animal {
move() { console.log("Moving"); }
}
class Bird extends Animal {
fly() { console.log("Flying"); }
}
const parrot = new Bird();
parrot.move(); // ✅ 继承父类方法
typescript
扩展说明:
- 继承链:
- 子类(
Bird
)继承父类(Animal
)的所有公共和受保护成员 - 私有成员(
private
)不会被继承 - 静态成员需要通过类名访问(如
Animal.staticMethod()
)
- 子类(
- 多级继承:
class LivingBeing {
breathe() { console.log("Breathing"); }
}
class Animal extends LivingBeing {
move() { console.log("Moving"); }
}
class Bird extends Animal {
fly() { console.log("Flying"); }
}
const bird = new Bird();
bird.breathe(); // ✅ 继承链方法
typescript
- 继承的限制:
- TypeScript不支持多重继承(一个类不能同时继承多个类)
- 可以通过Mixin模式模拟多重继承
💡 最佳实践:
- 保持继承层次扁平(建议不超过3层)
- 考虑使用组合替代深层次的继承
2.2 方法覆盖
class Person {
getName() { return "Tom"; }
}
class Employee extends Person {
getName() {
return `Employee: ${super.getName()}`; // 组合父类方法
}
}
const emp = new Employee();
console.log(emp.getName()); // 输出:"Employee: Tom"
typescript
扩展说明:
- 覆盖规则:
- 子类方法必须与父类方法兼容(参数和返回类型)
- 可以使用
super
调用父类原始实现 - 不能覆盖父类的私有方法
- 方法签名检查:
class Base {
greet(name: string) { console.log(`Hello ${name}`); }
}
class Derived extends Base {
greet(name: string, age?: number) { // ✅ 兼容签名
if(age) console.log(`Hello ${name}, age ${age}`);
else super.greet(name);
}
}
typescript
- 抽象方法要求:
abstract class Shape {
abstract getArea(): number;
}
class Circle extends Shape {
constructor(public radius: number) { super(); }
getArea() { // 必须实现抽象方法
return Math.PI * this.radius ** 2;
}
}
typescript
2.3 super关键字
2.3.1 调用父类构造函数
class Vehicle {
constructor(public type: string) {
console.log(`Vehicle ${type} created`);
}
}
class Car extends Vehicle {
constructor(public brand: string) {
super("Automobile"); // 必须先调用super
console.log(`Brand: ${brand}`);
}
}
const myCar = new Car("Toyota");
// 输出:
// Vehicle Automobile created
// Brand: Toyota
typescript
关键点:
super()
必须在访问this
之前调用- 如果没有显式构造函数,会自动调用父类的无参构造函数
- 构造函数参数可以通过
super
传递给父类
2.3.2 调用父类方法
class Printer {
print(msg: string) {
console.log(`[Base] ${msg}`);
}
}
class ColorPrinter extends Printer {
colors = ["Red", "Green", "Blue"];
print(msg: string) {
super.print(msg); // 调用父类实现
console.log(`With colors: ${this.colors.join(", ")}`);
}
}
const printer = new ColorPrinter();
printer.print("Hello");
// 输出:
// [Base] Hello
// With colors: Red, Green, Blue
typescript
高级用法:
- 动态super调用:
class A {
log() { console.log("A"); }
}
class B extends A {
log() {
const method = super.log;
method(); // 仍然指向父类实现
}
}
typescript
- super在静态方法中的限制:
class Parent {
static create() { return new Parent(); }
}
class Child extends Parent {
static create() {
// return super.create(); // ❌ 不能这样使用
return new Child();
}
}
typescript
2.4 继承中的属性初始化顺序
示例:
class Parent {
static staticField = console.log("Parent static");
instanceField = console.log("Parent instance");
constructor() { console.log("Parent constructor"); }
}
class Child extends Parent {
static staticField = console.log("Child static");
instanceField = console.log("Child instance");
constructor() {
super();
console.log("Child constructor");
}
}
new Child();
// 输出顺序:
// Parent static
// Child static
// Parent instance
// Parent constructor
// Child instance
// Child constructor
typescript
2.5 常见问题解答
Q1:为什么子类构造函数必须调用super()?
- JavaScript/TypeScript的类继承机制要求必须先初始化父类
- 底层实现上,子类实例的原型链依赖于父类构造函数的执行
Q2:如何防止类被继承?
class Final {
private constructor() {} // 使类不能被继承
static create() { return new Final(); }
}
// class Child extends Final {} // ❌ 编译错误
typescript
Q3:super能访问父类的私有成员吗?
- 不能,私有成员(
private
)只能在定义它们的类中访问 - 受保护成员(
protected
)可以通过super访问
2.6 延伸学习资源
3. 构造函数
3.1 基本用法
参数属性简写
class Product {
constructor(
public id: string, // 自动声明并初始化public属性
private price: number, // 自动声明private属性
protected stock?: number // 可选参数
) {
console.log(`Product ${id} created`);
}
}
const laptop = new Product("P100", 999);
typescript
扩展说明:
- 参数属性特性:
- 将参数声明和属性赋值合并为一步
- 支持所有访问修饰符(public/private/protected/readonly)
- 编译后会展开为传统的属性声明和赋值
- 等效传统写法:
class ProductTraditional {
public id: string;
private price: number;
constructor(id: string, price: number) {
this.id = id;
this.price = price;
console.log(`Product ${id} created`);
}
}
typescript
- 使用场景对比:
场景 参数属性 传统写法 简单初始化 ✅ 更简洁 ❌ 冗余 需要额外处理 ❌ 受限 ✅ 更灵活 只读属性 readonly param
this.param = value
💡 最佳实践:
- 对于简单的属性初始化优先使用参数属性
- 需要复杂初始化逻辑时使用传统写法
构造函数重载
class Product {
constructor(id: string);
constructor(id: string, price: number);
constructor(public id: string, public price?: number) {
this.price = price ?? 0; // 默认值处理
}
}
const p1 = new Product("P100");
const p2 = new Product("P200", 999);
typescript
3.2 初始化顺序
完整生命周期
实际示例:
class Parent {
static staticField = console.log("Parent static");
instanceField = console.log("Parent instance");
constructor() { console.log("Parent constructor"); }
}
class Child extends Parent {
static staticField = console.log("Child static");
instanceField = console.log("Child instance");
constructor() {
super();
console.log("Child constructor");
}
}
new Child();
/* 输出顺序:
Parent static
Child static
Parent instance
Parent constructor
Child instance
Child constructor
*/
typescript
关键规则
- 必须遵循的顺序:
- 静态字段按继承顺序初始化
- 实例字段按继承顺序初始化
- 构造函数必须从父类到子类执行
- 常见错误:
class Buggy extends Parent {
field = console.log(this.parentProp); // ❌ 访问过早
constructor() {
console.log(super()); // ❌ super()必须第一句
}
}
typescript
- 异步初始化模式:
class AsyncInit {
private initialized: Promise<void>;
constructor() {
this.initialized = this.init();
}
private async init() {
// 异步初始化逻辑
}
async ready() {
return this.initialized;
}
}
typescript
3.3 高级技巧
私有构造函数
class Singleton {
private static instance: Singleton;
private constructor() {}
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
// const s = new Singleton(); // ❌ 不能直接实例化
const s = Singleton.getInstance(); // ✅ 唯一访问点
typescript
构造函数返回值
class Strange {
constructor() {
return { surprise: "hello" }; // 可以返回替代对象
}
}
console.log(new Strange()); // 输出:{ surprise: "hello" }
typescript
3.4 常见问题解答
Q1:为什么参数属性不能使用装饰器?
- 这是TypeScript的当前限制
- 解决方法:使用传统属性声明方式
Q2:如何防止构造函数被外部调用?
- 使用
private constructor
实现单例模式 - 或者使用工厂函数替代直接构造
Q3:构造函数能声明为异步函数吗?
- 不能直接声明为
async constructor
- 替代方案:使用初始化方法或工厂函数
3.5 延伸学习资源
4. 类实现接口
4.1 implements关键字
基础实现
interface Loggable {
log(msg: string): void;
severity?: number; // 可选属性
}
class Service implements Loggable {
log(msg: string) {
console.log(`[SERVICE] ${msg}`);
}
// severity属性可选,可以不实现
}
typescript
扩展说明:
- 严格检查:
- 必须实现接口中所有非可选成员
- 方法参数和返回类型必须完全匹配
- 可以添加额外属性和方法
- 多接口实现:
interface Serializable {
serialize(): string;
}
class AdvancedService implements Loggable, Serializable {
log(msg: string) { /*...*/ }
serialize() { return "serialized"; }
}
typescript
- 动态实现检查:
function checkImplementation(obj: Loggable) {
if (!obj.log) throw new Error("Invalid implementation");
}
typescript
4.2 接口约束类结构
复杂约束示例
interface Identifiable {
id: string;
createdAt: Date;
updatedAt?: Date;
}
class User implements Identifiable {
constructor(
public id: string,
public createdAt: Date,
public updatedAt?: Date
) {}
// 可以添加额外方法
update() {
this.updatedAt = new Date();
}
}
typescript
高级用法:
- 使用泛型接口:
interface Repository<T> {
get(id: string): T;
save(entity: T): void;
}
class UserRepository implements Repository<User> {
get(id: string) { /*...*/ }
save(user: User) { /*...*/ }
}
typescript
- 抽象类与接口结合:
abstract class BaseService implements Loggable {
abstract log(msg: string): void;
commonMethod() { /*...*/ }
}
typescript
4.3 继承关系对比
详细对比表格
特性 | 类继承类 (extends) | 类实现接口 (implements) | 接口继承接口 (extends) |
---|---|---|---|
获得实现 | ✅ | ❌ | ❌ |
获得类型 | ✅ | ✅ | ✅ |
多继承/实现 | ❌ (单继承) | ✅ (多实现) | ✅ |
访问修饰符 | 保留 | 必须public | 不适用 |
运行时影响 | 有 | 无 | 无 |
抽象方法要求 | 可选 | 必须实现 | 不适用 |
典型应用场景
- extends:
- 代码复用
- 建立is-a关系
class Bird extends Animal { /*...*/ }
typescript - implements:
- 多态设计
- 契约式编程
class PDFExporter implements Exportable { /*...*/ }
typescript - interface extends:
- 接口组合
- 类型扩展
interface Auditable extends Timestamped { auditor: string; }
typescript
4.4 高级模式
接口隔离原则实践
// 拆分为细粒度接口
interface Readable {
read(): string;
}
interface Writable {
write(data: string): void;
}
class FileHandler implements Readable, Writable {
read() { /*...*/ }
write(data: string) { /*...*/ }
}
typescript
条件类型实现
interface Conditional<T extends string | number> {
value: T;
process(): T extends string ? string : number;
}
class StringHandler implements Conditional<string> {
value = "hello";
process() { return this.value.toUpperCase(); }
}
typescript
4.5 常见问题解答
Q1:为什么实现接口的方法必须是public的?
- 接口定义的是公共契约
- 私有方法属于实现细节,不应通过接口暴露
Q2:一个类可以实现多个接口吗?
- 可以,用逗号分隔
- 需要满足所有接口的要求
class A implements B, C, D { /*...*/ }
typescript
Q3:接口能约束静态成员吗?
- TypeScript 5.0+ 支持
interface Constructable {
new (): any;
staticMethod(): void;
}
typescript
4.6 延伸学习资源
5. 接口继承类
5.1 基本语法
接口继承类的完整示例
class Control {
private state: any;
protected config = {};
public id = "ctrl";
setState(newState: any) {
this.state = newState;
}
}
interface SelectableControl extends Control {
select(): void;
// 继承Control的所有成员类型:
// - private state (仅类型)
// - protected config
// - public id/setState
}
typescript
关键特性:
- 类型继承:
- 获得类的所有成员的类型定义
- 包括private/protected成员(仅类型信息)
- 实现要求:
- 实现接口的类必须满足所有成员的类型约束
- 但不继承具体实现逻辑
💡 提示:
这种模式常见于UI库设计,如需要扩展基础控件功能时。
5.2 关键限制
深度解析私有成员限制
class Base {
private secret = 123;
protected key = "abc";
}
interface Enhanced extends Base {
enhance(): void;
}
// ❌ 非法实现 - 缺少私有成员
class BadImpl implements Enhanced {
enhance() {}
key = "xyz"; // 仍然报错
}
// ✅ 合法实现 - 通过继承
class GoodImpl extends Base implements Enhanced {
enhance() {
console.log(this.key); // 可访问protected成员
// console.log(this.secret); // ❌ 仍不可访问private
}
}
typescript
限制原理:
- 类型系统需要保证私有成员的独占性
- 只有原类及其子类能"看到"私有成员的类型定义
- 这是TypeScript对JavaScript私有字段的模拟实现
5.3 继承内容特性
成员继承详细说明
成员类型 | 是否继承类型 | 是否继承实现 | 实现限制 |
---|---|---|---|
public | ✅ | ❌ | 必须实现 |
protected | ✅ | ❌ | 必须来自继承链 |
private | ✅ | ❌ | 必须原类/子类 |
static | ❌ | ❌ | 不适用 |
可视化继承流程
5.4 实际应用场景
场景1:UI组件扩展
class UIControl {
private _width = 0;
get width() { return this._width; }
}
interface Resizable extends UIControl {
resize(scale: number): void;
}
class Slider extends UIControl implements Resizable {
resize(scale: number) {
// 可以访问继承的width属性
console.log(`Resizing from ${this.width}`);
}
}
typescript
场景2:领域模型设计
class DomainEntity {
private id: string;
protected createdAt: Date;
}
interface Auditable extends DomainEntity {
getAuditLog(): string[];
}
class User extends DomainEntity implements Auditable {
getAuditLog() {
return [`Created at ${this.createdAt}`]; // 可访问protected
}
}
typescript
5.5 常见问题解答
Q1:为什么接口能继承私有成员?
- 继承的是类型信息而非实际值
- 保证类型系统的完备性
- 实际访问仍受原访问规则限制
Q2:如何绕过私有成员限制?
- 使用类型断言(不推荐)
- 重构设计,改用protected或public
- 使用装饰器模式替代继承
Q3:接口继承类后能添加新属性吗?
- 可以,接口可以自由扩展
interface Extended extends Control {
newProp: number; // 新增属性
}
typescript
5.6 延伸学习资源
↑